from ctypes import *
from ctypes.wintypes import *

dll = WinDLL('game')

_dll_version=dll.Version()
assert _dll_version==1, 'Unknown version of game.dll: %r'%_dll_version

dll.GetBoolean.restype=BOOL
dll.GetString.restype=c_char_p
dll.GetDouble.restype=c_double

class StackError(Exception): pass

class Stack(object):
    def __init__(self):
        self.hnd=dll.Open()

    def Close(self):
        dll.Close(self.hnd)
        self.hnd=None

    def _docall(self,meth,*par):
        self.SetTop(0)
        for x in par:
            self.Push(x)
        err=getattr(dll,meth)(self.hnd)
        if err==0:
            if len(self)==1: return self[0]
            elif len(self)==0: return None
            else: return tuple(self)
        else:
            self.SetTop(0)
            self.Push('GetError')
            self.Push(err)
            assert dll.Query(self.hnd)==0,'GetError call failed!'
            raise StackError('Error %r while calling %s%r'%(self[0],par[0],tuple(par[1:])))

    def Query(self,*a):
        return self._docall('Query',*a)

    def Execute(self,*a):
        return self._docall('Execute',*a)

    def GetValue(self,idx,vtype=None):
        if idx>dll.GetTop(self.hnd):
            raise IndexError('Index %d not on stack'%idx)
        if vtype is None:
            dt=dll.GetType(self.hnd,idx)
            if dt==0:
                raise IndexError('Index %d not on stack'%idx)
            if dt>4 or dt==2:
                raise TypeError('Invalid type: %d'%dt)
            vtype={1:bool,3:int,4:str}[dt]

        if vtype is bool: return bool(dll.GetBoolean(self.hnd,idx))
        elif vtype is str: return dll.GetString(self.hnd,idx)
        elif vtype is int: return dll.GetInteger(self.hnd,idx)
        elif vtype is float: return dll.GetDouble(self.hnd,idx)
        else: raise TypeError('Unknown type: %r'%type)

    def Push(self,val):
        if isinstance(val,bool): return dll.PushBoolean(self.hnd,BOOL(val))
        elif isinstance(val,int): return dll.PushInteger(self.hnd,c_int(val))
        elif isinstance(val,float): return dll.PushDouble(self.hnd,c_double(val))
        elif isinstance(val,str): return dll.PushStrVal(self.hnd,c_char_p(val))

    def GetTop(self):
        return dll.GetTop(self.hnd)

    def SetTop(self,idx):
        return dll.SetTop(self.hnd,idx)

    def __getitem__(self,idx):
        return self.GetValue(idx+1)

    def __len__(self):
        return self.GetTop()

    def __del__(self):
        if self.hnd is not None:
            if dll is None:
                raise RuntimeError('Oh crap! The DLL got freed before stack 0x%08x could be closed.'%self.hnd)
            self.Close()
